﻿using System;
using System.Collections.Generic;
using System.Linq;
using Test30.Core;

namespace Test30.Engine
{
        public class FeatureFactory
    {
        public FeatureFactory(IFeatureWriter writer, int depth)
        {
            _writer = writer;
            _depth = depth;
        }

        public void CreateFeature()
        {
            MakeCaverns();
            MakeStairs();
            Populate(_floors, 10, 10, _depth);
        }

        private void MakeCaverns()
        {
            RandomFillMap();
            GeneratorCaverns();
            CleanMap();
        }
        private void MakeStairs()
        {
            Vec tl, tr, bl, br;
            tl = tr = bl = br = Vec.Zero;
            for (int i = 1; i < _writer.Bounds.Height; i++)
            {
                if (tl == Vec.Zero)
                {
                    StairRange(0, 0, i, ref tl);
                }
                if (tr == Vec.Zero)
                {
                    StairRange(_writer.Bounds.Width - i, 0, i, ref tr);
                }
                if (bl == Vec.Zero)
                {
                    StairRange(0, _writer.Bounds.Height - i, i, ref bl);
                }
                if (br == Vec.Zero)
                {
                    StairRange(_writer.Bounds.Width - i, _writer.Bounds.Height - i, i, ref br);
                }
                if (tl != Vec.Zero &&
                    tr != Vec.Zero &&
                    bl != Vec.Zero &&
                    br != Vec.Zero) break;
            }

            if (tl.Distance(br) > tr.Distance(bl))
            {
                _writer.SetTile(tl, TileType.StairsDown);
                _writer.SetTile(br, TileType.StairsUp);
                _writer.SetStartPos(tl);
            }
            else
            {
                _writer.SetTile(tr, TileType.StairsDown);
                _writer.SetTile(bl, TileType.StairsUp);
                _writer.SetStartPos(tr);
            }
        }
        private void StairRange(int x, int y, int r, ref Vec i)
        {
            var rect = new Rect(x, y, r, r);
            foreach (Vec pos in rect)
            {
                if (_writer.GetTile(pos) == TileType.Floor)
                {
                    i = pos;
                    return;
                }
            }
        }
        private void CleanMap()
        {
            var floors = new List<Vec>();
            foreach (Vec pos in _writer.Bounds)
            {
                if (_writer.GetTile(pos) == TileType.Floor) floors.Add(pos);
            }
            if (floors.Count < 1) throw new ArgumentNullException();


            var step = new List<Vec>();
            List<List<Vec>> groups = new List<List<Vec>>();

            var temp = new List<Vec>();
            //初始条件
            step.Add(floors[0]);
            _writer.SetDirty(floors[0], true);

            while (floors.Count > 0)
            {
                temp.Clear();
                //建立step
                foreach (Vec pos in step)
                {
                    var neighbors = HasNeighbor(pos);
                    if (neighbors.Count > 0)
                    {
                        neighbors.ForEach(o => _writer.SetDirty(o,true));
                        temp.AddRange(neighbors);
                    }
                }
                step = temp.ToList();
                if (step.Count == 0)
                {
                    List<Vec> group = floors.Where(o => _writer.GetDirty(o)).ToList();
                    if (group.Count / (float)_writer.Bounds.Size.Area > _writer.Options.MinWalls / 100.0f)
                    {
                        SetMap(group);
                        return;
                    }
                    groups.Add(group);
                    group.ForEach(o => floors.Remove(o));
                    if (floors.Count > 0)
                    {
                        step.Add(floors[0]);
                        _writer.SetDirty(floors[0], true);
                    }
                }
            }
            if (groups.Count > 0)
            {
                foreach (List<Vec> list in groups)
                {
                    if (list.Count / (float)_writer.Bounds.Size.Area > _writer.Options.MinWalls / 100.0f)
                    {
                        SetMap(list);
                        return;
                    }
                }
                if (floors.Count < 1)
                    MakeCaverns();
            }
        }
        private void SetMap(List<Vec> floors)
        {
            _floors = floors;
            foreach (Vec pos in _writer.Bounds)
            {
                _writer.SetTile(pos, TileType.Wall);
            }
            floors.ForEach(o => _writer.SetTile(o,TileType.Floor));
        }
        private void GeneratorCaverns()
        {
            Array2D<TileType> temp = new Array2D<TileType>(_writer.Bounds.Width, _writer.Bounds.Height);
            for (int i = 0; i < 3; i++)
            {
                foreach (Vec pos in _writer.Bounds)
                {
                    temp[pos] = PlaceWallLogic(pos);
                }
                foreach (Vec pos in temp.Bounds)
                {
                    _writer.SetTile(pos, temp[pos]);
                }
            }
        }
        private TileType PlaceWallLogic(Vec pos)
        {
            //方案A
            /*
            int count = GetAdjacentWalls(pos, 1, 1);
            Tile temp;
            if (Map[pos].Type == TileType.Wall)
                temp = (count >= 4) ? Wall :Floor;
            else
                temp = (count >= 5) ? Wall : Floor;
            return temp;
             */
            //方案B
            int f = GetAdjacentWalls(pos, 1, 1);
            int g = GetAdjacentWalls(pos, 2, 2);

            if (f >= 5 || g <= 2)
                return TileType.Wall;
            return TileType.Floor;
        }
        private int GetAdjacentWalls(Vec pos, int scopeX, int scopeY)
        {
            int startX = pos.X - scopeX;
            int startY = pos.Y - scopeY;
            int endX = pos.X + scopeX;
            int endY = pos.Y + scopeY;

            int iY;

            int wallCounter = 0;

            for (iY = startY; iY <= endY; iY++)
            {
                int iX;
                for (iX = startX; iX <= endX; iX++)
                {
                    if (!(iX == pos.X && iY == pos.Y))
                    {
                        if (IsWall(iX, iY))
                        {
                            wallCounter += 1;
                        }
                    }
                }
            }
            return wallCounter;
        }
        private List<Vec> HasNeighbor(Vec pos)
        {
            List<Vec> temp = new List<Vec>();
            for (int y = pos.Y - 1; y <= pos.Y + 1; y++)
            {
                for (int x = pos.X - 1; x <= pos.X + 1; x++)
                {
                    if (!(x == pos.X && y == pos.Y))
                    {
                        if (IsWall(x, y) || IsDirty(x, y))
                            continue;
                        temp.Add(new Vec(x, y));
                    }
                }
            }
            return temp;
        }
        private bool IsWall(int x, int y)
        {
            // Consider out-of-bound a wall
            if (IsOutOfBounds(x, y)) return true;

            if (_writer.GetTile(x,y) == TileType.Wall) return true;

            if (_writer.GetTile(x, y) == TileType.Floor) return false;

            return false;
        }
        private bool IsDirty(int x, int y)
        {
            if (IsOutOfBounds(x, y)) return true;

            if (_writer.GetDirty(new Vec(x,y))) return true;

            return false;
        }
        private bool IsOutOfBounds(int x, int y)
        {
            if (x < 0 || y < 0)
            {
                return true;
            }
            if (x > _writer.Bounds.Width - 1 || y > _writer.Bounds.Height - 1)
            {
                return true;
            }
            return false;
        }
        private void RandomFillMap()
        {
            var bounds = _writer.Bounds;
            foreach (Vec pos in bounds)
            {
                if (pos.X <= bounds.Left + 1 || pos.X >= bounds.Right - 2 || pos.Y >= bounds.Top - 2 ||
                    pos.Y <= bounds.Bottom + 1)
                    _writer.SetTile(pos, TileType.Wall);
                else
                {
                    _writer.SetTile(pos,
                        _writer.Options.PercentAreWalls >= Rng.Int(1, 101) ? TileType.Wall : TileType.Floor);
                }
            }
        }

        private void Populate(List<Vec> floors, int monsterDensity, int itemDensity, int depth)
        {
            // test every open tile
            foreach (Vec pos in floors)
            {
                _writer.Populate(pos, monsterDensity, itemDensity, depth);
            }
        }


        private readonly IFeatureWriter _writer;
        private readonly int _depth;
        private List<Vec> _floors;
    }

}
